package com.seguriapp.dao;

import static com.google.common.collect.Lists.newArrayList;
import static org.hibernate.criterion.Restrictions.between;
import static org.hibernate.criterion.Restrictions.eq;
import static org.hibernate.criterion.Restrictions.ge;
import static org.hibernate.criterion.Restrictions.ilike;
import static org.hibernate.criterion.Restrictions.le;
import static org.hibernate.criterion.Restrictions.or;

import java.util.Calendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.hibernate.Criteria;
import org.hibernate.criterion.Disjunction;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.springframework.stereotype.Repository;

import com.seguriapp.base.GenericDaoImpl;
import com.seguriapp.configuration.GlobalConfigLoader;
import com.seguriapp.configuration.MapRelationsListLoader;
import com.seguriapp.entities.Complaint;
import com.seguriapp.util.CrimeDescription;
import com.seguriapp.util.CriminalItem;
import com.seguriapp.util.SearchFilterEntity;
import com.seguriapp.util.Util;

/**
 * Data access object for {@link Complaint} entities using Hibernate.
 * 
 * @author Tania Gonzales (taniadgv@gmail.com)
 */
@Repository
public class ComplaintDaoImpl extends GenericDaoImpl<Complaint> implements ComplaintDao {

  private static final String LONGITUDE = "crimeLongitude";
  private static final String LATITUDE = "crimeLatitude";
  private static final String DATE_FIELD = "crimeDate";
  private static final String NAMES_FIELD = "names";
  private static final String LASTNAMES_FIELD = "lastNames";

  public ComplaintDaoImpl() {
    super(Complaint.class);
  }

  @SuppressWarnings("unchecked")
  public List<CriminalItem> getCriminalActivityMap2(final double longitude, final double latitude) {
    final int height = 1000000;
    final int width = 1000000;
    final int length = 1000000;
    final int minRadioZone = 5;
    final List<Complaint> complaintLIst = getSession().createCriteria(Complaint.class).add(between(LATITUDE, latitude - height, latitude))
        .add(between(LONGITUDE, longitude, longitude + width)).list();
    final List<CriminalItem> criminalItemsList = newArrayList();
    for (final Complaint complaint : complaintLIst) {
      final double crimeLat = complaint.getCrimeLatitude();
      final double crimeLong = complaint.getCrimeLongitude();
      int longFactor = 0;
      int latFactor = 0;
      while (longitude + (length * longFactor) <= crimeLong) {
        longFactor++;
      }
      while (latitude - (length * latFactor) >= crimeLat) {
        latFactor++;
      }
      boolean newRadioFound = false;
      for (CriminalItem criminalItem : criminalItemsList) {
        if (criminalItem.getLatitude() > latitude - (length * latFactor) && criminalItem.getLatitude() < latitude - (length * (latFactor - 1))
            && criminalItem.getLongitude() < longitude + (length * longFactor)
            && criminalItem.getLongitude() > longitude + (length * (longFactor - 1))) {
          newRadioFound = true;
          if (!(criminalItem.getLatitude() + criminalItem.getRadio() > crimeLat && criminalItem.getLatitude() - criminalItem.getRadio() < crimeLat
              && criminalItem.getLongitude() + criminalItem.getRadio() > crimeLong && criminalItem.getLongitude() - criminalItem.getRadio() < crimeLong)) {
            criminalItem.setRadio(Util.distance(criminalItem.getLongitude(), criminalItem.getLatitude(), crimeLong, crimeLat));
            criminalItem.setLatitude((criminalItem.getLatitude() + crimeLat) / 2);
            criminalItem.setLongitude((criminalItem.getLongitude() + crimeLong) / 2);
          }
        }
      }
      if (!newRadioFound) {
        final CriminalItem criminalItem = new CriminalItem();
        criminalItem.setLatitude(crimeLat);
        criminalItem.setLongitude(crimeLong);
        criminalItem.setRadio(minRadioZone);
        criminalItemsList.add(criminalItem);
      }
    }
    return criminalItemsList;
  }

  @SuppressWarnings("unchecked")
  public List<CriminalItem> getCriminalActivityMap(final double longitude, final double latitude) {
    final List<Complaint> complaintLIst = getSession().createCriteria(Complaint.class).list();
    final List<CriminalItem> criminalItemsList = newArrayList();
    for (final Complaint complaint : complaintLIst) {
      final CriminalItem criminalItem = new CriminalItem();
      criminalItem.setLatitude(complaint.getCrimeLatitude());
      criminalItem.setLongitude(complaint.getCrimeLongitude());
      criminalItem.setRadio(0);
      // criminalItem.setCategory(complaint.getCategory());
      criminalItemsList.add(criminalItem);
    }
    return criminalItemsList;
  }

  @SuppressWarnings("unchecked")
  public List<Complaint> listComplaintsOrderByDate() {
    return getSession().createCriteria(Complaint.class).addOrder(Order.desc(DATE_FIELD)).list();
  }

  public void setComplaintVisibility(final Long complaintId, final boolean visible) {
    final Complaint complaint = findById(complaintId);
    complaint.setMapVisivility(visible);
    save(complaint);
  }

  @SuppressWarnings({ "unchecked", "rawtypes" })
  public List<CriminalItem> getCrminalActivityMap(final List<Long> mapOptions, final int zoomLevel, final int deviceId, final int allResults) {
    final Criteria criteria = getSession().createCriteria(Complaint.class);
    if (mapOptions != null) {
      final Disjunction disjunction = Restrictions.disjunction();
      for (final Long crimeCategoryId : mapOptions) {
        disjunction.add(eq("category.id", crimeCategoryId));
      }
      criteria.add(disjunction);
    }
    final List<Complaint> complaintList = criteria.add(eq("mapVisivility", true)).list();

    if (allResults != 0) {
      return getCriminalItemListFromList(complaintList);
    }
    HashMap response = new HashMap();
    Map<Integer, Double> relationMap = MapRelationsListLoader.getInstance().getMapRelationsList().getRelationsList();
    if (GlobalConfigLoader.getInstance().getGlobalConfig().getMobileDeviceId() == deviceId) {
      relationMap = MapRelationsListLoader.getInstance().getMapRelationsList().getMobileRelationsList();
    }

    final double zoneSide = relationMap.get(zoomLevel);
    for (final Complaint complaint : complaintList) {
      final long latZone = Double.valueOf(complaint.getCrimeLatitude() / zoneSide).longValue();
      final long longZone = Double.valueOf(complaint.getCrimeLongitude() / zoneSide).longValue();

      if (response.containsKey(latZone) && ((HashMap) response.get(latZone)).containsKey(longZone)) {
        final CriminalItem criminalItem = (CriminalItem) ((HashMap) response.get(latZone)).get(longZone);
        boolean found = false;
        if (criminalItem.getCrimeDescriptionList() != null) {
          for (final CrimeDescription crimeDescription : criminalItem.getCrimeDescriptionList()) {
            if (crimeDescription.getCategory().equals(complaint.getCategory())) {
              crimeDescription.setCount(crimeDescription.getCount() + 1);
              found = true;
              break;
            }
          }
        }
        if (!found) {
          final CrimeDescription crimeDescription = new CrimeDescription();
          crimeDescription.setCategory(complaint.getCategory());
          crimeDescription.setCount(1);
          if (criminalItem.getCrimeDescriptionList() == null) {
            final List<CrimeDescription> crimeDescriptionList = newArrayList();
            crimeDescriptionList.add(crimeDescription);
            criminalItem.setCrimeDescriptionList(crimeDescriptionList);
          } else {
            criminalItem.getCrimeDescriptionList().add(crimeDescription);
          }
        }
      } else {
        final CriminalItem criminalItem = new CriminalItem();
        criminalItem.setLatitude(zoneSide * latZone + zoneSide / 2);
        criminalItem.setLongitude(zoneSide * longZone + zoneSide / 2);
        criminalItem.setRadio(0);
        final List<CrimeDescription> crimeDescriptionList = newArrayList();
        final CrimeDescription crimeDescription = new CrimeDescription();
        crimeDescription.setCategory(complaint.getCategory());
        crimeDescription.setCount(1);
        crimeDescriptionList.add(crimeDescription);
        criminalItem.setCrimeDescriptionList(crimeDescriptionList);

        HashMap responseDetail = new HashMap();
        responseDetail.put(longZone, criminalItem);
        response.put(latZone, responseDetail);
      }
    }
    return getCriminalItemListFromHashMap(response);
  }

  @SuppressWarnings("unchecked")
  public List<Complaint> listComplaintsOrderByColumn(final SearchFilterEntity searchFilter) {
    final Criteria criteria = setCriteriaForQuery(searchFilter);
    if (searchFilter.getColumnSort() != null) {
      if (searchFilter.isAscending()) {
        criteria.addOrder(Order.asc(searchFilter.getColumnSort()).ignoreCase());
        if (NAMES_FIELD.equals(searchFilter.getColumnSort())) {
          criteria.addOrder(Order.asc(LASTNAMES_FIELD).ignoreCase());
        }
      } else {
        criteria.addOrder(Order.desc(searchFilter.getColumnSort()).ignoreCase());
        if (NAMES_FIELD.equals(searchFilter.getColumnSort())) {
          criteria.addOrder(Order.desc(LASTNAMES_FIELD).ignoreCase());
        }
      }
    } else {
      criteria.addOrder(Order.desc(DATE_FIELD));
    }
    if (searchFilter.isAllResults()) {
      return criteria.list();
    } else {
      return criteria.setMaxResults(searchFilter.getPageSize()).setFirstResult((searchFilter.getPage() - 1) * searchFilter.getPageSize()).list();
    }
  }

  public long getComplaintsTotalPages(final SearchFilterEntity searchFilter) {
    final Criteria criteria = setCriteriaForQuery(searchFilter);
    criteria.setProjection(Projections.rowCount());
    final long total = Long.valueOf(criteria.uniqueResult().toString());
    long pages = 0;
    if (searchFilter.getPageSize() != 0) {
      pages = total / searchFilter.getPageSize();
      if (total % searchFilter.getPageSize() > 0) {
        pages++;
      }
    }
    return pages;
  }

  @SuppressWarnings("unchecked")
  public List<Complaint> getCrminalActivityMap(final List<Long> crimeCategories) {
    final Criteria criteria = getSession().createCriteria(Complaint.class);
    if (crimeCategories != null) {
      final Disjunction disjunction = Restrictions.disjunction();
      for (final Long crimeCategoryId : crimeCategories) {
        disjunction.add(eq("category.id", crimeCategoryId));
      }
      criteria.add(disjunction);
    }
    return criteria.add(eq("mapVisivility", true)).list();
  }

  private Criteria setCriteriaForQuery(final SearchFilterEntity searchFilter) {
    final Criteria criteria = getSession().createCriteria(Complaint.class);
    // by start date
    if (searchFilter.getStartDate() != null) {
      criteria.add(ge(DATE_FIELD, searchFilter.getStartDate()));
    }
    // by end date
    if (searchFilter.getEndDate() != null) {
      Calendar cal = Calendar.getInstance();
      cal.setTime(searchFilter.getEndDate());
      cal.set(Calendar.HOUR_OF_DAY, cal.getMaximum(Calendar.HOUR_OF_DAY));
      cal.set(Calendar.MINUTE, cal.getMaximum(Calendar.MINUTE));
      cal.set(Calendar.SECOND, cal.getMaximum(Calendar.SECOND));
      criteria.add(le(DATE_FIELD, cal.getTime()));
    }
    // by police station
    if (searchFilter.isSearchByPolStation() && searchFilter.getPoliceStationId() != 0) {
      criteria.add(eq("entity.id", searchFilter.getPoliceStationId()));
    } else {
      // by latitude and longitude
      if (searchFilter.getLatitude() != 0 && searchFilter.getLongitude() != 0 && searchFilter.getDistance() != 0) {
        criteria.add(
            between(LATITUDE, searchFilter.getLatitude() - searchFilter.getDistance(), searchFilter.getLatitude() + searchFilter.getDistance())).add(
            between(LONGITUDE, searchFilter.getLongitude() - searchFilter.getDistance(), searchFilter.getLongitude() + searchFilter.getDistance()));
      }
    }
    // by names and lastnames
    if (searchFilter.getNames() != null && !searchFilter.getNames().isEmpty()) {
      criteria.add(or(ilike(NAMES_FIELD, searchFilter.getNames(), MatchMode.ANYWHERE),
          ilike(LASTNAMES_FIELD, searchFilter.getNames(), MatchMode.ANYWHERE)));
    }
    // by user entities
    if (searchFilter.getIdsList() != null && !searchFilter.getIdsList().isEmpty()) {
      final Disjunction disjunction = Restrictions.disjunction();
      for (final Long entityId : searchFilter.getIdsList()) {
        disjunction.add(eq("entity.id", entityId));
      }
      criteria.add(disjunction);
    }
    return criteria;
  }

  @SuppressWarnings("rawtypes")
  private List<CriminalItem> getCriminalItemListFromHashMap(final HashMap response) {
    final List<CriminalItem> criminalItemsList = newArrayList();

    Iterator iterator = response.entrySet().iterator();

    while (iterator.hasNext()) {
      HashMap detail = (HashMap) ((Map.Entry) iterator.next()).getValue();
      Iterator secondIterator = detail.entrySet().iterator();
      while (secondIterator.hasNext()) {
        criminalItemsList.add((CriminalItem) ((Map.Entry) secondIterator.next()).getValue());
      }
    }

    return criminalItemsList;
  }

  private List<CriminalItem> getCriminalItemListFromList(final List<Complaint> complaintList) {
    final List<CriminalItem> criminalItemsList = newArrayList();
    for (final Complaint complaint : complaintList) {
      final CriminalItem criminalItem = new CriminalItem();
      criminalItem.setLatitude(complaint.getComplaintLatitude());
      criminalItem.setLongitude(complaint.getComplaintLongitude());
      final CrimeDescription crimeDescription = new CrimeDescription();
      crimeDescription.setCategory(complaint.getCategory());
      final List<CrimeDescription> crimeDescriptionList = newArrayList();
      crimeDescriptionList.add(crimeDescription);
      criminalItem.setCrimeDescriptionList(crimeDescriptionList);
      criminalItemsList.add(criminalItem);
    }
    return criminalItemsList;
  }

}
